#!/usr/bin/perl -w

#=============================================================================
#      C A P T I V A T O R  - G W
#
#	A "captive portal" style network authorization system
#
#       Copyright 2005 The University of Wisconsin Board of Regents
#	Licensed and distributed under the terms of the Perl Artistic 
#	License, see doc/LICENSE for details.
#
#       Written by: Dale W. Carder, dwcarder@doit.wisc.edu
#       Network Services Group
#       Division of Information Technology
#       University of Wisconsin at Madison
#
#	For more about Captivator-gw, see:
#       http://net.doit.wisc.edu/~dwcarder/captivator-gw
#
#       This code uses the RPC implementation as shown in O'Reilly's Advanced 
#       Perl book. 
#
#=============================================================================


package Captivator;


#=====================================================
#    U S E  A N D  R E Q U I R E
#=====================================================

use lib ("/usr/local/Captivator/lib");
use strict;				# enforces better style
use Exporter;				# easy perl module functions
use vars qw(@ISA @EXPORT);		# perl module variables
use Sys::Syslog;			# we report lots to syslog!
use IPC::Open3;				# to open handles to shell calls
use RPC;				# RPC functions from O'Reilly & supplied w/ Captivator-gw
use DBI;				# database connection, from CPAN
use NetAddr::IP;			# ip address manipulation, from CPAN
#use Net::LDAP;
use Authen::Radius;			# from CPAN
use DateTime;
use DateTime::Format::DBI;
use Net::SMTP::TLS;
use IO::Socket;
use Digest::Perl::MD5 'md5_hex';
use FreezeThaw qw(thaw);

#=====================================================
#   E X P O R T
#=====================================================
my $VERSION = 1.01;
@ISA = qw(Exporter);
@EXPORT = qw(	whoIsLoggedIn getServerHost ip2VlanNum authenticateUser 
        expireAllHosts expireHosts removeHosts tablePurge 
        runServer slog isMacAddr matchMacAddr isIpAddr matchIpAddr 
        getMacFromArpTable tableInsert addToCurrentUsers tableDelete
        readConfig dbConnect untaint_usernm untaint_passwd
        printInfoPage printSysErrPage printLoginForm printRedirectPage
        printLoginFailedPage radiusAcct startupDB printAllreadyLoggedInPage
        printGuestNoticePage validateAndAddEmail guestTimeout generatePass
        emailGuestPassword expireGuests insertIntoExpireUsers findUserId
        checkAuthZ printBlockedPage rpcConnect callIptables do_arpForHost
		printInvalidEmailPage rpcsecret rpcSend rpcDisconnect rpcSet
	     );



#======================================================================
#    B E G I N   C O N F I G U R A T I O N   S E C T I O N
#

my $ConfigFile = "/usr/local/Captivator/etc/captivator.config";	# path to config file

#
#      E N D   C O N F I G U R A T I O N   S E C T I O N
#==========================================================================


#======================================================================
#    G L O B A L  V A R I A B L E S 
#
#	Do not edit these values.  Use the config file listed 
#	above instead.
#

$ENV{PATH} = "/bin"; 	# a safe path
my $global_dbh;		# global & somewhat persistant database connection
my $debugLvl;           # how much debugging info we want: 0-7, gets sent to syslog
my $syslogfacility;     # what syslog facility to use
my $arp_table;          # path to arp table in /proc
my $arping;             # path to arping
my $arpopts;            # arping options
my $iptablescmd;        # path to iptables
my $server_host;        # address we run the backend server on
my $server_port;        # port the backend server runs on
my $rpcsecret;		# shared secret with backend
my $ssl_cert;		# RPC ssl certificate
my $ssl_key;		# RPC ssl key file
my $db_host;            # host the database server is running on
my $db_name;            # database name on the server
my $db_user;            # username to connect to database server
my $db_pass;            # password to connect to database server 
my $fw_hostname;        # the hostname of the firewall
my $markPackets;	# whether to mark packets for QoS
my $radius_server;      # hostname of the primary radius server
my $radius_pass;        # password for the primary radius server
my $radius_server2;     # hostname of the secondary radius server
my $radius_pass2;       # password for the secondary radius server
my $radius_nasip;	# ip address to use as the NAS IP to the radius server
my %authnet;		# data structure to define vlans and such
my $vConfigCmd;         # path to vconfig
my $ifConfigCmd;        # path to ifconfig
my $brCtlCmd;           # path to brctl
my $tcCmd;		# path to tc
my $workingdir;         # unix path to html files, no trailing slash
my $templatedir;        # unix path to html template files, no trailing slash
my $main_tmpl;          # main html template page
my $login_tmpl;         # login template 
my $guest_page;         # Guest login info page
my $loginfailed_tmpl; 	# login failed template 
my $syserr_tmpl;        # system error template
my $blocked_tmpl;       # blocked user template
my $loggedinallready_tmpl;  # logged in allready error template
my $redirect_page;      # redirect page, shown after sucessful authz
my $DefaultRedirect; 	# If the user doesn't specify a redirect page, send them here
my $AuthZMode;		# default auth mode for built in captivator authorization
my $UseLDAPAuthZ;	# do we use LDAP for auth-z or not?
my $UseApacheAuthN;	# do we want to use apache's auth-n?
my $UseRadiusAuthN;	# do we want to use radius?
my $Radius_acct_server;	# hostname of the radius accounting server
my $Radius_acct_secret;	# password for the radius accounting server
my $guestTimeout;       # how long can guests have temporary access
my $smtpserver;         # SMTP server name
my $smtphello;          # HELO value
my $smtpuser;           # SMTP username
my $smtppass;           #      password
my $smtpfrom;           # from address for message

our $IsTheServer = 0;
my $client;
#
#
#======================================================================




#=====================================================
#    P R O T O T Y P E S
#=====================================================
sub readConfig($);
sub whoIsLoggedIn($);
sub getServerHost($);
sub ip2VlanNum($);
sub authenticateUser($$);
sub authenticateUserByRadius($$);
sub authenticateUserByGuestTable($$);
sub validateAndAddEmail($);
sub readFromCurrentUsers();
sub insertIntoExpireUsers($$);
sub expireAllHosts();
sub expireHosts();
sub processIptablesList();
sub removeHosts();
sub archiveUsers(%);
sub addToCurrentUsers($$$$$);
sub dbConnect();
sub tablePurge();
sub callIptables($;$;$;$;$);
sub tableList();
sub tableFlush($);
sub tableInsert($$$$);
sub tableDelete($$$$);
sub runServer();
sub slog($$);
sub isMacAddr($);
sub matchMacAddr($);
sub isIpAddr($);
sub matchIpAddr($);
sub getMacFromArpTable($);
sub buildIptablesCmd($;$;$;$;$);
sub arpForHost($$$);
sub do_arpForHost($$);
sub checkAuthZ($$);
sub checkLDAPAuthZ($);
sub checkCaptivatorAuthZ($$);
sub untaint_usernm($);
sub untaint_passwd($$);
sub printSysErrPage();
sub printBlockedPage($);
sub printRedirectPage($$$);
sub printInvalidEmailPage();
sub printGuestNoticePage($);
sub printInfoPage();
sub printLoginFailedPage($$);
sub printLoginForm($$);
sub startupDB();
sub radiusAcct($$$$$);
sub generatePass();
sub emailGuestPassword($$);
sub rpcConnect();
sub rpcSet($;$);
sub rpcDisconnect();
sub rpc(@);
sub rpcSend($;$;$;);
sub findUserId($);
sub rpcsecret();

# ok, now let's load in our config:
readConfig($ConfigFile);


# ------------- BEGIN SUBROUTINES ---------------------------------

#===============================================================================
#
#	R E A D  C O N F I G
#
#	Reads in configration file and sets global variables for this library
#
#===============================================================================
sub readConfig($) {

	my $config_file = shift;

	open(CONFIG, "$config_file")
		or die "couldn't...\n";

	my $config = '';

	# sadly, this is how we untaint the config file contents.
	while (<CONFIG>) {
		my $configln = $_;
		$configln =~ /(.*)/;
		$configln = "$1 \n";
		$config = $config . $configln;
	}
	close CONFIG;

	eval $config;
  		die "Couldn't eval your config line: $@\n" if $@;

	return $config;
}



#===============================================================================
#	
#	W H O  I S  L O G G E D  I N
#
#	Arg1:	string containing an ip address
#
#	Returns username of the ip address that is currently allowed through the fw
#
#===============================================================================
sub whoIsLoggedIn($) {

        slog(7,"Starting: isLoggedIn");
	my $ip = shift;

	slog(7,"Got ip: $ip");

        # convert the supplied ipv4 address into an integer for the database
        my $ipobj = new NetAddr::IP($ip);
        my $ipnum = $ipobj->numeric();

	# get the mac address
	my $mac = getMacFromArpTable($ip);

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"isLoggedIn couldn't connect to the database");
                return(0);
        }

        # read entries from sql table 
        my $sth;
        eval { $sth = $dbh->prepare("SELECT username,auth_method FROM currentusers WHERE fwhostname=? AND v4ip=? AND macaddress=?"); } ;
        if ($@) {
                slog(3,"isLoggedIn couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: expireHosts");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        eval { $sth->execute($fw_hostname,$ipnum,$mac); };
        if ($@) {
                slog(3,"isLoggedIn couldn't query the currentusers table");
                slog(4,$sth->errstr);
                slog(7,"Done: expireHosts");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        my ($username,$authmethod);
        $sth->bind_columns(\($username,$authmethod));
	$sth->fetch;

	if (defined($username)) {
		slog(5,"$ip - $mac is currently logged in as $username, auth method $authmethod");
        	slog(7,"Done: isLoggedIn");
		return($username,$authmethod);
	}

	else {
		slog(5,"$ip - $mac is not currently logged in.");
        	slog(7,"Done: isLoggedIn");
		return(0);
	}

} # end of whoIsLoggedIn()


#===============================================================================
#
#       G E T  S E R V E R  H O S T
#
#	Arg1:	vlan number
#
#	Returns the ip address of the bridge interface for this vlan
#
#===============================================================================
sub getServerHost($) {
	my $vl = shift;
	return $authnet{$vl}{"br_int_name"};
}



#===============================================================================
#
#       I P  2  V L A N  N U M
#
#	Gives the internal vlan number a given ip address should be on.
#
#	Arg1:	string containting an IP address
#
#	Returns: vlan number, if known
#	Returns: 0, otherwise
#
#	Uses the global variable %authnet
#
#=============================================================================== 
sub ip2VlanNum($) {

	slog(7,"Starting: ip2VlanNum");

	my $ip = shift;
	slog(7,"Given: $ip");

	# validate the argument was an ip address 
        if (!isIpAddr($ip)) {
                slog(7,"ip2VlanNum Recieved invalid input: $ip");
                slog(7,"Done: ip2VlanNum");
                return 0;
        }

	my $ipobj = new NetAddr::IP($ip);	# create a netaddr ip object

	# iterate through the networks
	foreach my $vlan (keys %authnet) {
		my @subnets = @{$authnet{$vlan}{'v4subnet'}};
		slog(7,$subnets[0]);
		foreach my $subnet (@subnets) {
			slog(7,"Looking at vlan $vlan, subnet $subnet");
		
			my $subnetobj = new NetAddr::IP($subnet);

			# if the ip is inside the subnet:
	
			if ($subnetobj->contains($ipobj)) {

				slog(6,"ip2VlanNum: $ip is in the authnet vlan $vlan");
				slog(7,"Done: ip2VlanNum");
				return($vlan);
	
			} else {

				slog(7,"ip2VlanNum: $ip is not in this authnet subnet");
			}
		}

	} # end foreach loop

	# we couldn't find an entry.
	return(0);

} # end of sub ip2VlanNum()

#===============================================================================
#
#       A U T H E N T I C A T E  U S E R  
#
#	Wrapper for user authentication.  This module decides how we're
#	going to do authentication and does it.  If you were going to
#	implement fallback authentication (like, try radius first, ldap
#	second, local db third, etc) this is where you would do that
#
#       Arg1:   username
#       Arg2:   password
#
#       Returns: 1, if authentication was successful
#       Returns: 0, if authentication was not successful
#
#===============================================================================
sub authenticateUser($$) {

        slog(7,"Starting: authenticateUser");
        my $user = untaint_usernm(shift);
        my $pass = untaint_passwd(shift,$user);

	my $authenticated = 0;

	# if we let apache do the authentication (say, via pubcookie)
	# then we return 1 and let apache do the work.
	if ($UseApacheAuthN) { return(1); }

### Todo, make guest an option

	if ($user =~ /\@/) { #this is an email address => guest
		if(authenticateUserByGuestTable($user,$pass)) { return(1); }
	} elsif ($UseRadiusAuthN) {
		$user =~ s/\@\.//g;
		if(authenticateUserByRadius($user,$pass)) { return(1); }
	}

	return(0);


} # end of authenticateUser()	




#===============================================================================
#
#	A U T H E N T I C A T E  U S E R  B Y  R A D I U S
#
#	authenticate a user via radius.  (of course!)
#
#	Arg1:	username
#	Arg2:	password
#
#	Returns: 1, if authentication was successful
#	Returns: 0, if authentication was not successful
#
#	Uses global variables $radius_server and $radius_pass
#
#	Thanks for this subroutine goes to Janet Plato and Chih Chen.
#
#===============================================================================
sub authenticateUserByRadius($$) {

	slog(7,"Starting: authenticateUserByRadius");

	my $user = shift;
	my $pass = shift;

	# WE DO NOT WANT TO RUN WITH THE NEXT LINE IN PRODUCTION!!!
	slog(7,"authenticateUserByRadius Input: $user");

	if (! (defined($user)) && (defined($pass)) ) {
		slog(3,"authenticateUserByRadius called without valid arguments");
		return(0);
	}

	# create connection object
	my $r = new Authen::Radius(Host => $radius_server, Secret => $radius_pass);

	if (! defined($r)) {
		slog(3,"Can't connect to radius server $radius_server, will try $radius_server2");

		$r = new Authen::Radius(Host => $radius_server2, Secret => $radius_pass2);

		if (! defined($r)) {
			slog(3,"Can't connect to radius server $radius_server2, and we're out of options.");
			slog(7,"Done: authenticateUserByRadius");
			return(0);
		}
	}

	if ($r->check_pwd($user,$pass,$radius_nasip)) {
		slog(5,"Radius Authentication successful for $user");
		return(1);
	} else {
		slog(5,"Radius Authentication failed for $user");
		slog(7,"Done: authenticateUserByRadius");
		return(0);
	}

	slog(7,"Done: authenticateUserByRadius");

} # end of authenticateUserByRadius()

#===============================================================================
#
#	A U T H E N T I C A T E  U S E R  B Y  G U E S T  T A B L E
#
#	authenticate a user via SQL table of guests (email and pass)
#
#	Arg1:	username (email address)
#	Arg2:	password
#
#	Returns: 1, if authentication was successful
#	Returns: 0, if authentication was not successful
#
#	Nicholas Bachmann
#	Housing Information Techonology Office
#	University of Michigan
#
#===============================================================================

sub authenticateUserByGuestTable($$) {

	slog(7,"Starting: authenticateUserByGuestTable");
	
	my $user = shift;
	my $pass = shift;

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"isLoggedIn couldn't connect to the database");
                return(0);
        }

        # read entries from sql table
        my $sth;

        eval { $sth = $dbh->prepare("SELECT email,password FROM guests WHERE email=?"); };
        if ($@) {
                slog(3,"authenticateUserByGuestTable couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: authenticateUserByGuestTable");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };
        eval { $sth->execute($user); };
        if ($@) {
                slog(3,"authenticateUserByGuestTable couldn't query the guests table");
                slog(4,$sth->errstr);
                slog(7,"Done: authenticateUserByGuestTable");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        my ($db_password,$email);
        $sth->bind_columns(\($email,$db_password));
        $sth->fetch;
	if ($pass eq $db_password && $email eq $user) {
		slog(5,"Guest Authentication successful for $user");
		return(1);
	} else {
		slog(5,"Guest Authentication failed for $user: ($email).");
		slog(6,"SELECT email,password FROM guests WHERE email='$user'");
                slog(7,"Done: authenticateUserByGuestTable");
                return(0);
	}
	slog(7,"Done: authenticateUserByGuestTable");
}

#===============================================================================
#
#	V A L I D A T E  A N D  A D D  E M A I L
#
#	validate email address, generate a password, add to guest table, send notice
#
#	Arg1:	email
#
#	Returns: 1, if successful
#	Returns: 0, if not successful
#
#	Nicholas Bachmann
#	Housing Information Techonology Office
#	University of Michigan
#
#===============================================================================

sub validateAndAddEmail($) {
    my $email = shift;
    unless($email =~ /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/) {
        slog(4,"Invalid email provided by user: $email");
        return 0; 
    }
    slog(7,"Starting: validateAndAddEmail for $email");
 
    my $pass = generatePass();

    my $dbh = dbConnect();
    if (!$dbh) { # couldn't connect to database
        slog(3,"validateAndAddEmail couldn't connect to the database");
    }
    my $sth;

    eval { $sth = $dbh->prepare("SELECT created,password FROM guests WHERE email=?"); };
       if ($@) {
        slog(3,"validateAndAddEmail couldn't prepare the select sql statement");
        slog(4,$dbh->errstr);
        slog(7,"Done: validateAndAddEmail");
        $sth->finish();
        $dbh->disconnect;
        return(0);
    }; 
    eval { $sth->execute($email); };
    if ($@) {
        slog(3,"validateAndAddEmail couldn't query the guests table");
        slog(4,$sth->errstr);
        slog(7,"Done: validateAndAddEmail");
        $sth->finish();
        $dbh->disconnect;
        return(0);
    };

    my $created = 0;
    $sth->bind_columns(\($created,$pass));
    $sth->fetch;

    if($created ne 0) { 
        slog(5,"User $email already registered on $created, resending password.");
    } else {
        slog(5,"Creating user $email ($pass $created)");
        eval { $sth = $dbh->prepare("INSERT INTO guests (email,password,created) VALUES (?,?,NOW())"); };
        if ($@) {
            slog(3,"validateAndAddEmail couldn't prepare the insert sql statement");
            slog(4,$dbh->errstr);
            slog(7,"Done: validateAndAddEmail");
            $sth->finish();
            $dbh->disconnect;
            return(0);
        };
        eval { $sth->execute($email,$pass); };
        if ($@) {
            slog(3,"validateAndAddEmail couldn't update the guests table");
            slog(4,$sth->errstr);
            slog(7,"Done: validateAndAddEmail");
            $sth->finish();
            $dbh->disconnect;
            return(0);
        };
    }
    emailGuestPassword($email,$pass);
    slog(7,"Done: validateAndAddEmail for $email");
    return 1;
}

sub generatePass() {
    my $pass = `/usr/local/bin/gpw 1`; #TODO: make this a variable
    chomp($pass);
    return $pass;
}

#This would be for guest users
sub emailGuestPassword($$) { #TODO: make smtp stuff into variables
    my $email = shift;
    my $pass = shift; 
    my $mailer = Net::SMTP::TLS->new ($smtpserver,
		Timeout 	=> 60,
		Hello		=> $smtphello,
		Port		=> 587,
		User		=> $smtpuser,
		Password	=> $smtppass,  
    );
    $mailer->mail($smtpfrom);
    $mailer->to($email);
    $mailer->data;
    $mailer->datasend("To: $email\n");
    $mailer->datasend("From: ResComp <hito.helpdesk\@umich.edu>\n");
    $mailer->datasend("Subject: ResComp Guest Access\n\n");
    $mailer->datasend("Welcome to the University Housing network!\n");
    $mailer->datasend("\nTo login in the future, use the following credentials:\n");
    $mailer->datasend("  Username: $email\n");
    $mailer->datasend("  Password: $pass\n");
    $mailer->datasend("\nUse the following link to login immediately:\n");
    $mailer->datasend("https://maizeport.housing.umich.edu/index.pl?username=$email&password=$pass&from_email=yes\n");
    $mailer->datasend("\nIf you have any questions, please contact the\nHITO/Rescomp Helpdesk at (734) 647-1133.");
    $mailer->datasend("\n");
    $mailer->dataend;
    $mailer->quit; 
    slog(7, "Emailed $email password $pass");
}

#===============================================================================
#
#	R E A D  F R O M  C U R R E N T  U S E R S
#
#	Reads in currentusers sql table, and returns it as a hash of hashes
#	keyed by mac address
#
#===============================================================================
sub readFromCurrentUsers() {

	slog(7,"Starting: readFromCurrentUsers");

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"readFromCurrentUsers couldn't connect to the database");
        }

        # read entries from sql table 
        my $sth;
        eval { $sth = $dbh->prepare("SELECT username,fwhostname,macaddress,v4ip,v6ip,logintime,packets,bytes,intvlan,user_id,auth_method FROM currentusers WHERE fwhostname=?"); };
        if ($@) {
                slog(3,"readFromCurrentUsers couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: readFromCurrentUsers");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };
	$dbh = dbConnect();

        eval { $sth->execute($fw_hostname); };
        if ($@) {
                slog(3,"readFromCurrentUsers couldn't query the currentusers table");
                slog(4,$sth->errstr);
                slog(7,"Done: readFromCurrentUsers");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

	my ($username,$fw,$mac,$v4ip,$v6ip,$logintime,$packets,$bytes,$intvlan,$user_id,$authmethod);
        $sth->bind_columns(\($username,$fw,$mac,$v4ip,$v6ip,$logintime,$packets,$bytes,$intvlan,$user_id,$authmethod));

	my %tablelist;

        # for entry in the table, we iterate through this loop
        while($sth->fetch) {

		slog(7,"got row: $username,$fw,$mac,$v4ip,$logintime,$packets,$bytes,$intvlan,$user_id,$authmethod");

		$tablelist{$user_id}{'username'} = $username;
		$tablelist{$user_id}{'fw'} = $fw;
		$tablelist{$user_id}{'v4ip'} = $v4ip;
		$tablelist{$user_id}{'logintime'} = $logintime;
		$tablelist{$user_id}{'packets'} = $packets;
		$tablelist{$user_id}{'bytes'} = $bytes;
		$tablelist{$user_id}{'intvlan'} = $intvlan;
		$tablelist{$user_id}{'mac'} = $mac;
                $tablelist{$user_id}{'auth_method'} = $authmethod;
	}

	slog(7,"Done: readFromCurrentUsers");
	return %tablelist;

} # end readFromCurrentUsers()


#===============================================================================
#
#	I N S E R T  I N T O  E X P I R E  U S E R S
#
#	Inserts a row into the expireusers sql table
#
#	OLD Arguments: username, mac, v4ip, v6ip, reason, intvlan, fw
#	NEW Arguments: user_id, reason
#
#===============================================================================
sub insertIntoExpireUsers($$) {

	slog(7,"Starting: insertIntoExpireUsers");
            
	my $user_id = shift;
	my $reason = shift;

	my $sth3;

	slog(7,"will expire user_id: $user_id, reason: $reason");

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"insertIntoExpireUsers couldn't connect to the database");
        }

        eval { $sth3 = $dbh->prepare("INSERT INTO expireusers (user_id,logoffreason) VALUES (?,?)"); };
        if ($@) {
                slog(3,"insertIntoExpireUsers couldn't prepare the insert sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: insertIntoExpireUsers");
                $sth3->finish();
                $dbh->disconnect;
                return(0);
        };

        eval { $sth3->execute($user_id,$reason); };
        if ($@) {
                slog(3,"insertIntoExpireUsers couldn't update the expireusers table");
                slog(4,$sth3->errstr);
                slog(7,"Done: insertIntoExpireUsers");
                $sth3->finish();
                $dbh->disconnect;
                return(0);
        };

	slog(7,"Done: insertIntoExpireUsers");
	return(1);

} # end of sub insertIntoExpireUsers() 

sub findUserId($) {
        slog(7,"Starting: useridForUsername");
        my $ip = shift;
	slog(7,"Got ip: $ip");

        # convert the supplied ipv4 address into an integer for the database
        my $ipobj = new NetAddr::IP($ip);
        my $ipnum = $ipobj->numeric();

	# get the mac address
	my $mac = getMacFromArpTable($ip);

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"findUserId couldn't connect to the database");
                return(0);
        }

        # read entries from sql table 
        my $sth;
        eval { $sth = $dbh->prepare("SELECT user_id FROM currentusers WHERE fwhostname=? AND v4ip=? AND macaddress=?"); } ;
        if ($@) {
                slog(3,"findUserId couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: expireHosts");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        eval { $sth->execute($fw_hostname,$ipnum,$mac); };
        if ($@) {
                slog(3,"findUserId couldn't query the currentusers table");
                slog(4,$sth->errstr);
                slog(7,"Done: expireHosts");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        my ($user_id);
        $sth->bind_columns(\($user_id));
	$sth->fetch;

	if (defined($user_id)) {
		slog(5,"$ip - $mac is currently logged in as $user_id");
        	slog(7,"Done: findUserId");
		return($user_id);
	}

	else {
		slog(5,"$ip - $mac is not currently logged in.");
        	slog(7,"Done: findUserId");
		return undef;
	}


}

#===============================================================================
#
#	E X P I R E  A L L  H O S T S
#
#	Takes all users from currentusers table and copies them to the
#	expireusers table for removal.  This sort of function is something
#	that would happen nightly via cron.
#
#===============================================================================
sub expireAllHosts() {

	slog(7,"Starting: expireAllHosts");

	my %userlist = readFromCurrentUsers();
	my $reason = "kickall";

	foreach my $user_id (keys %userlist) {

		insertIntoExpireUsers($user_id,$reason);
	}

	slog(7,"Done: expireAllHosts");
	return(1);
			
} # end sub expireAllHosts()

#===============================================================================
#
#	E X P I R E  H O S T S
#
#	figures out who should be removed from the firewall
#
#	It does this by:
#	- comparing the packet counters in iptables to the packet counters in activeusers
#	- If the counters have not incremented, then {
#		- call arping for their mac address (arp for them) and see if they answer
#		- if not, put an entry in the expireusers table for them to be removed 
#		  from the firewall, noting that expiration was the reason for the boot
#	} else {
#		- update the counters in the activeusers table
#	}
#
#
#	expireHosts and expireUsers (which actually does the dirtywork, and will 
#	also expire users called for by the management console) should be run 
#	together via cron something like every 10 minutes.  expireUsers could be
#	run even more often, in theory.
#
#	
#===============================================================================
sub expireHosts() {

        slog(7,"Starting expireHosts");

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"expireHosts couldn't connect to the database");
        }

	# we keep a list of users to be removed from the database called to_be_expired.

	# list out the current iptables rules, including packet & byte counters
	# process this into a useful data struture 
	my %tableoutput = processIptablesList();

	# select from currentusers where fwhostname = $fw_hostname

	my %userlist = readFromCurrentUsers();

	# for entry in the table, we iterate through this loop
        foreach my $user_id (keys %userlist) {

		my $username = $userlist{$user_id}{'username'};
                my $fw = $userlist{$user_id}{'fw'};
		my $mac = $userlist{$user_id}{'mac'};
                my $v4ip = $userlist{$user_id}{'v4ip'};
                my $v6ip = $userlist{$user_id}{'v6ip'};
                my $logintime = $userlist{$user_id}{'logintime'};
                my $packets = $userlist{$user_id}{'packets'};
                my $bytes = $userlist{$user_id}{'bytes'};
                my $intvlan = $userlist{$user_id}{'intvlan'};
                my $authmethod = $userlist{$user_id}{'auth_method'};

		#translate into human readable IP
		my $ipobj = new NetAddr::IP($v4ip);
                my $v4ipstr = $ipobj->addr();

		# the packet couter from iptables
		my $tablepkts = $tableoutput{$v4ipstr}{"packets"};

		if (! defined($tablepkts)) {

			slog(5,"Got row: $username,$fw,$mac,$v4ipstr,$logintime,$packets,$bytes,$intvlan");
			slog(5,"That row doesn't have a corresponding entry in the firewall rules");
		} else {

			slog(7,"Packet counter from iptables $tablepkts");
		}

		# if the packet counters from iptables are > to the counters from sql {
		if ($tablepkts > $packets) {

			slog(7,"Not expiring $username,$mac,$v4ipstr, they're sending traffic");

			# then we update the sql counters
			# update currentusers were username=blah, etc set packets=x, counters=y
			my $sth2;
			eval { $sth2 = $dbh->prepare("UPDATE currentusers SET packets=? WHERE user_id=?"); };
			if ($@) {
                      		slog(3,"expireHosts couldn't prepare the update sql statement");
                       		slog(4,$dbh->errstr);
                       		slog(7,"Done: expireHosts");
                       		$sth2->finish();
                       		$dbh->disconnect;
                       		return(0);
               		};

        		eval { $sth2->execute($tablepkts,$user_id); };
			if ($@) {
                       		slog(3,"expireHosts couldn't update the currentusers table");
                       		slog(4,$sth2->errstr);
                       		slog(7,"Done: expireHosts");
                       		$sth2->finish();
                       		$dbh->disconnect;
                       		return(0);
               		};

		} else { # otherwise, if the packet counters have not incremented, let's
			 # see if they are still around by arping for them.

                        # variable to track if we will remove the entry or not.
                        # assume that we will by setting it to 1.
                        my $remove = 1;

                        # track why they got expired, this is the defualt reason.
                        my $why = "no response to arp";

			if (arpForHost($v4ipstr,$intvlan,$mac)) {
				# then we hear from them via arp
				$remove=0;
			} else {
				# we did not hear from them via arp
				$remove=1;
			}

			# if we are going to remove them, we put them in the expireusers sql table
			# to be later removed by removeHosts.
			if ($remove) {

				# the reason we are expiring this entry
				# this must be one of the enumerated values from the sql column.
				my $reason = "expired";

				slog(5,"Expiring user: $user_id - $username, $why");

                        	insertIntoExpireUsers($user_id,$reason);

			} # end of if we are going to remove them

		} # end of else packet counters were not incremented

	} # end of while for each entry in currentusers

} # end of expireHosts()
	
sub expireGuests() {
        slog(7,"Starting expireGuests");

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"expireGuests couldn't connect to the database");
        }

	my %userlist = readFromCurrentUsers();

	# for entry in the table, we iterate through this loop
        foreach my $user_id (keys %userlist) {

		my $username = $userlist{$user_id}{'username'};
                my $fw = $userlist{$user_id}{'fw'};
		my $mac = $userlist{$user_id}{'mac'};
                my $v4ip = $userlist{$user_id}{'v4ip'};
                my $v6ip = $userlist{$user_id}{'v6ip'};
                my $logintime = $userlist{$user_id}{'logintime'};
                my $packets = $userlist{$user_id}{'packets'};
                my $bytes = $userlist{$user_id}{'bytes'};
                my $intvlan = $userlist{$user_id}{'intvlan'};
                my $authmethod = $userlist{$user_id}{'auth_method'};

		#translate into human readable IP
		my $ipobj = new NetAddr::IP($v4ip);
                my $v4ipstr = $ipobj->addr();

                #find how long they've been logged in
                my $logindatetime = DateTime::Format::DBI->new($dbh)->parse_datetime($logintime);
                my $now = DateTime->now(time_zone => 'local');
                my $deltaTime = $now->subtract_datetime($logindatetime)->delta_minutes;
                
		slog(7,"Got row: $user_id,$username,$fw,$mac,$v4ipstr,$logintime ($deltaTime minutes ago),$packets,$bytes,$intvlan,$authmethod");
                
                #See if they need to be expired for time limits
                if($authmethod eq 'temporary' && $deltaTime >= $guestTimeout) {
                        my $why = "guest access expired";
                        my $reason = "expired";
                        slog(5,"Expiring user: $user_id - $username, $why");
                       	insertIntoExpireUsers($user_id,$reason);    
                } elsif($authmethod eq 'temporary') {
                        slog(7,"$user_id not being expired, $deltaTime not enough time.");
                }
        }
}	

#===============================================================================
#
#	P R O C E S S  I P T A B L E S  L I S T
#
#	Calls tableList(), processes the output, and return it in an easy to 
#	use datastructure
#
#===============================================================================
sub processIptablesList() {

	slog(7,"Starting: processIptablesList");

	# get a list of chains we care about from %authnet
	my @chains;
	foreach my $vlankey (keys %authnet) {
		my $fw_chain = $authnet{$vlankey}{"fw_chain"};
		slog(7,"Vlan $vlankey is iptables chain: $fw_chain");
		push(@chains, $fw_chain);
	}

	# get the output from iptables -L
	my @tablelistoutput = tableList();

	if (scalar(@tablelistoutput) eq 0) {
		slog(3,"processIptablesList had a problem calling tableList?");
		# return(0);
	}

	# keeps track of whether we are looking at output from a chain we
	# care about or not.
	my $validchain = 0;
	
	my %output;	# our datastructure we will return, keys will be the ip address

	foreach my $line (@tablelistoutput) {

		# Wow, this really was too much debugging
                #slog(7,"inside a chain we care about: $validchain");
                # slog(7,"table line: $line");

		# if we're looking at data in a chain we care about
		if ($validchain) {

			# and the line is accept
			if ($line =~ m/(radius|temporary|guest|superuser)/ and $line !~ m/^Chain/) {  #TODO we should do this dynamically
				# split the line on whitespace
				my @lineitems = split(' ',$line);

				slog(7,"found IP: $lineitems[7], mac: $lineitems[10], bytes: $lineitems[1], pkts: $lineitems[0]");

				$output{$lineitems[7]}{"macaddr"} = $lineitems[10];
				$output{$lineitems[7]}{"bytes"} = $lineitems[1];
				$output{$lineitems[7]}{"packets"} = $lineitems[0];
			}
		} # end of if we're in a valid chain

		# match "Chain \w+" is the beginning of a chain
		if ($line =~ m/Chain\s(\w+)/) {

			slog(7,"This line marks the beginning of chain $1");

			# assume this chain is not interesting
			$validchain=0;

			# if we are looking at a chain that we care about (from @chains)
			foreach my $item (@chains) {
				if ($1 eq $item) { 
					$validchain=1; 
					slog(7,"$item is a chain we care about");
				}
			} 

		} # end of matching the Chain name

	} # end of line by line parsing of iptables -L

	slog(7,"Done: processIptablesList");
	return(%output);

} # end processIptablesList()



#===============================================================================
#
#	R E M O V E  H O S T S
#
#	reads in expireusers sql table (which is populated by expireHosts and
#	a management application) and does the dirtywork of removing hosts from 
#	the firewall and calling archiveuser to move the user from the currentusers
#	table to the pastusers table
#
#
# 	Why does it not currently really matter that removing the users from the
# 	iptables ruleset is not a batch process?  Yes, there is some overhead to
# 	all of the RPC to keep calling iptables, but that's probably nothing
# 	compared to the fact that we're shelling out each line to iptables anyway.
# 	TODO: This approach should be modified when we switch to using the C API 
# 	and can do a commit on a batch update of the rule removals.
#
#	One limitation to address is that the packet and byte counters for users
#	that logged themselves off or were booted off will not be as recent as we
#	may want.  It also may be worth noting what the reason for expiration was,
#	so we augmented the database format to support that.
#
#===============================================================================
sub removeHosts() {

	slog(7,"Starting removeHosts");

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"removeHosts couldn't connect to the database");
        }

	# read entries from sql table 
	my $sth;
	eval { $sth = $dbh->prepare("SELECT a.username,a.macaddress,a.v4ip,a.v6ip,b.logoffreason,a.intvlan,a.fwhostname,a.auth_method,b.user_id FROM currentusers AS a, expireusers AS b WHERE a.fwhostname=? AND a.user_id = b.user_id"); };
	if ($@) {
		slog(3,"removeHosts couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: removeHosts");
                $sth->finish();
                $dbh->disconnect;
		return(0);
	};

	eval { $sth->execute($fw_hostname); };
	if ($@) {
                slog(3,"removeHosts couldn't query the expireusers table");
                slog(4,$sth->errstr);
                slog(7,"Done: removeHosts");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

	my ($username,$macaddr,$v4ip,$v6ip,$reason,$vlan,$fw,$authmethod,$user_id);

	$sth->bind_columns(\($username,$macaddr,$v4ip,$v6ip,$reason,$vlan,$fw,$authmethod,$user_id));

	my %tempuserdata;	# temporary storage hash give to archiveusers
	my $sth2;		# sql statement handle for removeing users from expireusers table

	my $todo =0;	# do we have anything to do (were there any rows returned?)


	# for each user to be removed, we iterate through this loop
	while($sth->fetch) {


		slog(7,"Got row: $username,$macaddr,$v4ip,$reason,$vlan,$fw,$authmethod,$user_id");
		$todo=1;	# yes, there's something to do.

		# the ip address comes out of the database as an integer.  Convert this to
		# the more familiar dotted quad notation.
		my $v4ipstr = new NetAddr::IP "$v4ip";
		$v4ipstr = $v4ipstr->addr();

		# call iptables manipulation server to remove their entry
		if (tableDelete($v4ipstr,$macaddr,$vlan,$authmethod)) {
			slog(7,"removeHosts successfully had the firewall remove $v4ipstr - $macaddr");
		} else {
			slog(3,"removeHosts could not get the firewall to remove $v4ipstr - $macaddr");
		}

		# put data info into hash of hashes for calling archiveusers
		$tempuserdata{$user_id}{"reason"} = $reason;

		# remove the entry from the expireusers table

		eval { $sth2 = $dbh->prepare("DELETE FROM expireusers WHERE user_id=?"); };
		if ($@) {
			slog(3,"removeHosts couldn't prepare the select sql statement");
                        slog(4,$dbh->errstr);
                        $sth2->finish();
		}


		eval { $sth2->execute($user_id); };
		if ($@) {
                        slog(3,"removeHosts couldn't delete from the expireusers table");
			slog(4,"Delete from expireusers where user_id: $user_id");
                        slog(4,$sth2->errstr);
                        $sth2->finish();
                } else {

			slog(6,"removeHosts $fw_hostname removed user $username, $v4ipstr ($v4ip), $macaddr,vlan $vlan.");
                	$sth2->finish();

			# send radius accounting stop message 
			radiusAcct($username,$user_id,$macaddr,$v4ip,0)
		}

		

	} # end of sql row fetch

	# call archiveusers(users to be removed and pertinant data)

	if (! $todo) { 				# if no rows were returned from the database
		slog(6,"Removehosts has nothing to do at this time.");
		return(0);
	}

	if (archiveUsers(%tempuserdata)) { # if archival was successful
		slog(7,"removeHosts sucessfully called archiveUsers");
		slog(7,"Done: removeHosts");
		return(1);
	} else { 	# archival was not succesful
		slog(3,"removeHosts call of archiveUsers failed");
		slog(7,"Done: removeHosts");
		return(0);
	}

} # end of removeHosts()



#===============================================================================
#
#	A R C H I V E  U S E R S
#
#	remove information about users from the currentusers table, and
#	put it in the pastusers table with all of the information we know
#
#	Arg1:	A hash keyed on user_id's
#		- Currently the only element is "reason"
#
#	Returns: True, if the operation was successful
#	Returns: False, if there was a substantual error
#
#===============================================================================
sub archiveUsers(%) {

	slog(7,"Starting: archiveUsers");

	# TODO, verify these inputs were specified and were legit?
	my %users = @_;

	# initilize some variables once, instead of creating and destroying
	# them everytime we process the foreach loop below
	my $user_id = 0;
	my $user = 0;
	my $fw = 0;
	my $mac = 0;
	my $v4ip = 0;
	my $v6ip = 0;
	my $logintime = 0;
	my $pkts = 0;
	my $bytes = 0;
	my $reason = 0;
	my $vlan = 0;
	my $authmethod = 0;

	# connect to the database

       	my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
         	slog(3,"archiveUsers couldn't connect to the database");
       	}

	foreach my $user_id (keys %users) {

		# reset variables for safety
		$user =0;
		$fw = 0;
		$mac =0;
		$v4ip =0;
		$v6ip =0;
		$logintime =0;
		$pkts =0;
		$bytes =0;
		$vlan =0;
		$authmethod =0;

		$reason = $users{$user_id}{"reason"};

		# TODO, verify these inputs were specified and were legit
		slog(7,"archiveUsers processing user id: $user_id, reason: $reason");

        	my $sth;
		eval { $sth = $dbh->prepare("SELECT username,fwhostname,macaddress,v4ip,v6ip,logintime,packets,bytes,intvlan,auth_method FROM currentusers WHERE user_id=?"); };
		if ($@) {
                       	slog(3,"archiveUsers couldn't prepare the select sql statement");
                       	slog(4,$dbh->errstr);
                       	slog(7,"Done: archiveUsers");
                       	$sth->finish();
                       	$dbh->disconnect;
                       	return(0);
               	}

        	eval{ $sth->execute($user_id); };
		if ($@) {
                       	slog(3,"archiveUsers couldn't query the currentuers table");
                       	slog(4,$sth->errstr);
                       	slog(7,"Done: archiveUsers");
                       	$sth->finish();
                       	$dbh->disconnect;
                       	return(0);
               	}

		$sth->bind_columns(\($user,$fw,$mac,$v4ip,$v6ip,$logintime,$pkts,$bytes,$vlan,$authmethod));
        	$sth->fetch;
		$sth->finish;

		my $continue=1;	# should we continue processing or not.

		if (!($logintime)) { # then a row was not returned, just a sanity check
			slog(3,"archiveUsers couldn't an entry in the currentusers table for user_id: $user_id");
			# we really shouldn't bail and return zero here, we should just
			# process the next item in the array.
			$continue=0;
		}

		if ($continue) {
			eval{ $sth = $dbh->prepare("DELETE FROM currentusers WHERE user_id=?"); };
			if ($@) {
                       		slog(3,"archiveUsers couldn't prepare the delete sql statement");
				slog(4,"archiveUsers tried to delete user_id: $user_id");
                       		slog(4,$dbh->errstr);
                       		slog(7,"Done: archiveUsers");
                       		$sth->finish();
                       		$dbh->disconnect;
                       		return(0);
               		}

			slog(7,"archiveUsers deleting user_id: $user_id - $user");

			eval{ $sth->execute($user_id); };
			if ($@) {
                       		slog(3,"archiveUsers couldn't remove the entry from the currentusers table");
				slog(4,"archiveUsers tried to delete user_id: $user_id");
                       		slog(4,$sth->errstr);
                       		slog(7,"Done: archiveUsers");
                       		$sth->finish();
                       		$dbh->disconnect;
                       		return(0);
               		}

			$sth->finish;

			slog(7,"archiveUsers inserting: $user,$fw,$mac,$v4ip,$logintime,$pkts,$bytes,$reason,$vlan,$authmethod");

			eval{ $sth = $dbh->prepare("INSERT INTO pastusers VALUES (?,?,?,?,?,?,NOW(),?,?,?,?,?)" ); };
				if ($@) {
                       		slog(3,"archiveUsers couldn't prepare the insert sql statement");
				slog(4,"archiveUsers inserting: $user,$fw,$mac,$v4ip,$v6ip,$logintime,$pkts,$bytes,$reason,$vlan,$authmethod");
                       		slog(4,$dbh->errstr);
                       		slog(7,"Done: archiveUsers");
                       		$sth->finish();
                       		$dbh->disconnect;
                       		return(0);
               		}

        		eval{ $sth->execute($user,$fw,$mac,$v4ip,$v6ip,$logintime,$pkts,$bytes,$reason,$vlan,$authmethod); };
			if ($@) {
                       		slog(3,"archiveUsers couldn't insert the entry into the pastusers table");
				slog(4,"archiveUsers inserting: $user,$fw,$mac,$v4ip,$v6ip,$logintime,$pkts,$bytes,$reason,$vlan,$authmethod");
                       		slog(4,$sth->errstr);
                       		slog(7,"Done: archiveUsers");
                       		$sth->finish();
                      		$dbh->disconnect;
                       		return(0);
               		}
		} # end of if we should continue

        	$sth->finish;

	} # end of foreach loop that itereates through the array of user information.

	$dbh->disconnect;
	slog(7,"Done: archiveUsers");
	return(1);

} # end of archiveUsers()



#===============================================================================
#
#	A D D  T O  C U R R E N T  U S E R S
#
#	adds information into the currentusers table in the database
#
#	Arg1:	username (netid)
#	Arg2:	ipv4 address
#	Arg3:	mac address
#	Arg4:	internal vlan number
#
#	Returns: True, if the operation was successful
#	Returns: False, if there was an error
#
#===============================================================================
sub addToCurrentUsers($$$$$) {

	slog(7,"Starting: addToCurrentUsers");

	my $username = shift;
	my $ipaddr4 = shift;
	my $macaddr = shift;
	my $vlan = shift;
	my $authmethod = shift;

	if ($username !~ m/\w+/) {
		slog(3,"addToCurrentUsers Recieved invalid input username: $username");
		slog(7,"Done: addToCurrentUsers");
		return(0);
	}
	if (! (isIpAddr($ipaddr4)) ) {
                slog(3,"addToCurrentUsers Recieved invalid input ipaddr4: $ipaddr4");
                slog(7,"Done: addToCurrentUsers");
                return(0);
	}
	if (!(isMacAddr($macaddr)) ) {
                slog(3,"addToCurrentUsers Recieved invalid input macaddr: $macaddr");
                slog(7,"Done: addToCurrentUsers");
                return(0);
	}
	if ($vlan !~ m/\d+/) {
                slog(3,"addToCurrentUsers Recieved invalid input vlan: $vlan");
                slog(7,"Done: addToCurrentUsers");
                return(0);
	}

	# connect to database
	my $dbh = dbConnect();

	if (!$dbh) { # couldn't connect to database
		slog(3,"addToCurrentUsers couldn't connect to the database");
	}
	
	# convert the supplied ipv4 address into an integer for the database
	my $ipobj = new NetAddr::IP($ipaddr4);
	my $ipnum = $ipobj->numeric();

	slog(7,"$ipaddr4 is $ipnum");

	# TODO, replace the NULL with an ipv6 address.  see the database schema.
	my $sth;
	eval { $sth = $dbh->prepare("INSERT INTO currentusers (username,fwhostname,macaddress,v4ip,intvlan,logintime,packets,bytes,auth_method) VALUES (?,?,?,?,?,NOW(),0,0,?)"); };
	if ($@) {
		slog(3,"addToCurrentUsers couldn't prepare the sql statement");
		slog(4,$dbh->errstr);
		slog(7,"Done: addToCurrentUsers");
		$sth->finish();
		$dbh->disconnect;
		return(0);
	}

	eval { $sth->execute($username,$fw_hostname,$macaddr,$ipnum,$vlan,$authmethod); };
	if ($@) {
		slog(3,"addToCurrentUsers couldn't execute the sql statement");
		slog(4,$sth->errstr);
		slog(7,"Done: addToCurrentUsers");
		$sth->finish();
		$dbh->disconnect;
                return(0);
        };

	$sth->finish();

        my $sth2;
        eval { $sth2 = $dbh->prepare("SELECT LAST_INSERT_ID()"); };
        if ($@) {
                slog(3,"addToCurrentUsers couldn't prepare the sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: addToCurrentUsers");
                $sth2->finish();
                $dbh->disconnect;
                return(0);
        }

        eval { $sth2->execute(); };
        if ($@) {
                slog(3,"addToCurrentUsers couldn't execute the sql statement");
                slog(4,$sth2->errstr);
                slog(7,"Done: addToCurrentUsers");
                $sth2->finish();
                $dbh->disconnect;
                return(0);
        };

	my $insert_id=0;
        $sth2->bind_columns(\($insert_id));
        $sth2->fetch;
        $sth2->finish();
	slog(7,"addToCurrentUsers returning insert id $insert_id");

	$dbh->disconnect;
	slog(7,"Done: addToCurrentUsers");

	return($insert_id);

} # end of addToCurrentUsers()



#===============================================================================
#
#	D B  C O N N E C T
#
#	connects to the mysql database
#
#	Global variables used: $db_host, $db_user, $db_pass
#
#	Returns: database handle, if connect was successful
#	Returns: false, if operation failed
#
#===============================================================================
sub dbConnect() {

	slog(7,"Starting: dbConnect");


#	if ($global_dbh) {
#		slog(7,"dbConnect will use an allready active database connection");
#		slog(7,"Done: dbConnect");
#		return $global_dbh;
#	}

	my $dbh;
	my $dsn = "DBI:mysql:$db_name:$db_host";
	eval { 
		$dbh = DBI->connect($dsn, $db_user, $db_pass, {
			PrintError => 1,   
			RaiseError => 1    
        	});
	};

	if ($@) { 
		slog(3,"Can't connect to database server $dsn " . DBI->errstr);
		slog(7,"Done: dbConnect");
		return(0);
	} else {
		slog(6,"Database connection creation successful");
		slog(7,"Saving connection information");
		$global_dbh = $dbh;
	}

	slog(7,"Done: dbConnect");
	return($dbh)

} # end of dbConnect()

#===============================================================================
#
#	T A B L E  P U R G E
#
#	Removes all entries from captivator chains by flushing them.
#
#===============================================================================
sub tablePurge() {

	slog(7,"Starting: tablePurge");

	# get a list of chains and call iptables to flush them

        foreach my $vlan (keys %authnet) {
	
		my $chain = $authnet{$vlan}{'fw_chain'};

		slog(6,"tablePurge will purge chain $chain");
		tableFlush($chain);
        } 

	slog(7,"Done: tablePurge");

} # end of tablePurge


#===============================================================================
#	C A L L  I P T A B L E S
#
#	used by the server on behalf of the clients.  When the individual
#	table* subroutines talk to the server, this is the interface they are
#	doing it through on the server end.
#
#       Arg1: Scalar containing a character that specifies the iptables command:
#                       L - list 			hint: see IPTABLES(8)
#                       I - insert
#                       D - drop
#                       F - flush
#
#       If Arg1 is 'I' or 'D', the following three arguments are required:
#
#       Arg2: Source ipv4 address
#       Arg3: Source mac address
#       Arg4: Internal vlan number
#
#	If Arg1 is 'F', the following argument is required:
#
#	Arg2: table name
#
#       Returns: Scalar containing the iptables command to be executed
#       Returns: False, if something went wrong
#
#===============================================================================
sub callIptables($;$;$;$;$) {

        slog(3,"Starting: callIptables");

        my $cmd = shift;
       	my $srcip = 0;
       	my $srcmac = 0;
       	my $vlan = 0;
	my $chainname = 0;
	my $iptablescmdln ='';
	my $authmethod ='';

	if ($cmd =~ m/^[ID]$/) {
        	$srcip = shift if @_;
        	$srcmac = shift if @_;
        	$vlan = shift if @_;
		$authmethod = shift if @_;
        	slog(7,"Input: cmd: $cmd, srcip: $srcip, srcmac: $srcmac, intvlan: $vlan");

	} elsif ( $cmd eq 'F' ) {
		$chainname = shift;
        	slog(7,"Input: cmd: $cmd, chain: $chainname");
	}


        # verify our inputs are legit
        if ($cmd !~ m/^[LIDF]$/) {
                slog(3,"callIptables Recieved invalid command input: $cmd");
                slog(7,"Done: callIptables");
                return 0;
        }

	if ($cmd eq 'F') {
		if (!defined($chainname)) {
                	slog(3,"callIptables flush didn't recieve a chain name");
                	slog(7,"Done: callIptables");
                	return 0;
		}

		# build the iptables command
		$iptablescmdln = buildIptablesCmd($cmd,$chainname);
	}
		
        
        # if the command is I or D, then srcip and srcmac are required
        if ($cmd =~ m/^[ID]$/) {
                if (!isIpAddr($srcip)) {
                	slog(3,"callIptables Recieved invalid command input: $srcip");
                	slog(7,"Done: callIptables");
                	return 0;
        	}
        	if (!isMacAddr($srcmac)) {
                	slog(3,"callIptables Recieved invalid command input: $srcmac");
                	slog(7,"Done: callIptables");
                	return 0;
        	}
		if ($vlan !~ m/\d+/) {
                        slog(3,"callIptables Recieved invalid command input vlan: $vlan");
                        slog(7,"Done: callIptables");
                        return 0;
                }
                if ($authmethod !~ m/^[rtgs]/) {
                        slog(3,"callIptables Recieved invalid command input authmethod: $authmethod");
                        slog(7,"Done: callIptables");
                        return 0;
                }

		# build the iptables command
		$iptablescmdln = buildIptablesCmd($cmd,$srcip,$srcmac,$vlan,$authmethod);

	} # end if command is I or D

	if ($cmd eq 'L') {
		# build the iptables command
		$iptablescmdln = buildIptablesCmd($cmd);
	}


	# if building the iptables command fails,
	if (!$iptablescmdln) {
		slog(3,"callIptables ran buildIptablesCmd, which failed");
		slog(7,"Done: callIptables");
                return 0;
	} else {
		slog(6,"buildIptablesCmd returned \"$iptablescmdln\"");
	}

	# now it's time to execute the iptables command we got.

	# TODO:  Switch to using a better iptables API.
	# People may wonder why we shell out to iptables instead of using
	# the perl module in CPAN that can manipulate the rules through the
	# C API. http://search.cpan.org/~dpates/IPTables-IPv4-0.98/ 

	# Well, we could (and perhaps should) use that perl module,
	# but it is a bit harder to program it compared to shelling out during
	# the developement process for this program.

	# In the long run, we probably will want/need to use this interface
	# for scalibility and performance concerns.  There is also some
	# concern that the libiptc interface is somewhat doomed:
	# http://www.netfilter.org/documentation/FAQ/netfilter-faq-4.html#ss4.5
	
	# We also need to verify that 
	# http://search.cpan.org/~dpates/IPTables-IPv4-0.98/IPv6.pm may
	# implement source mac address filtering, which would be needed
	# in our application.

	unless (open(OUT,"$iptablescmdln 2>&1 |")) {
        	slog(3,"There was an error calling iptables.");
        	slog(4,"$!");
        	slog(7,"Done: callIptables");
        	return(0);
        }

	my @iptablesoutput = <OUT>;
	close OUT;

	# If we were listing the tables, we need to return that to the client
	if ($cmd eq 'L') {
        	slog(7,"Done: callIptables");
		return(@iptablesoutput);
	
	# now, for iptables commands 'I' and 'D', we may have something we
	# caught from iptables' STDERR.  If we do, then report the error
	# and return 0.  Otherwise, life is good and we return 1.
	} else {
		if (scalar(@iptablesoutput) ne 0) {
			slog(3,"callIptables ERROR: @iptablesoutput");
        		slog(7,"Done: callIptables");
			return(0);
		} else {
        		slog(7,"Done: callIptables");
			return(1);
		}
	}


} # end of callIptables()

sub rpcDisconnect() {
    if($client) {
        $client->close;
    }
}

sub rpcSet($;$) {
    $server_host = shift;
    $server_port = shift;
}

sub rpcConnect() {
    rpcDisconnect();
    my $client = IO::Socket::INET->new(
        PeerAddr => $server_host,
        PeerPort => $server_port,
        Proto    => 'tcp',  
                                      );
    unless($client) {
        warn "Connect to backend failed.";
        return undef;
    }
        #    
        #log in
        #
    $/ = "\n";
    my $header = <$client>;
    my $saltstr = <$client>;
    chomp($saltstr);
    if($saltstr) {
        my $hash = md5_hex($saltstr . $rpcsecret);
        print $client "$hash\n";
    } else {
        warn "No salt line\n";
        warn "I did get $header?\n";
    }
    my $status = <$client>;
    if($status =~ /denied/) {
        die "Access denied to Captivator backend.\n";
    }
    return $client;
}

sub rpcSend($;$;$;) {
        my $client = shift;
        my $command = shift;
        my $tries = shift;

        $tries = 1 unless $tries;

        my $response = "";
        my $fh = ${$client};
        if($fh) {
                $fh->flush;
                print $fh "CAPTIVATOR-PING\n";
                $response = <$fh>;
                chomp($response);
        }

        if ($response =~ /CAPTIVATOR-PONG/) {
                $_ = <$fh>; #get \cD 
                print $fh $command . "\n";
        } else {
                if($tries < 5) {
                        warn("Lost socket! Attempting reconnect to backend, try $tries.\n") if $tries > 1;
                        $$client = &rpcConnect();
                        rpcSend($client,$command,$tries + 1);
                } else {
                        die "Reconnect failed!";
                }
        }
}

sub rpc(@) {
    my $command = shift;
    die "Invalid rpc call--no command or invalid command given" unless ($command =~ /\w+/);

    my $args;
    if($_[0]) {
        $args = "\"" . shift(@_) . "\"";
    } else {
        $args = "";
    }
    for(@_) {
        $args .= ",'" . $_ . "'";
    }

    rpcSend(\$client,$command . "(" .  $args . ")",1);

    my $response = "";
    while (<$client>) {
        last if /^\cD/;
        $response .= $_;
    }
    chomp($response);

    return thaw($response) or warn ($command);
}



#===============================================================================
#	T A B L E  L I S T
#
#	Calls the server to list out the current iptables rules
#
#	Uses the global variables: $chain, $server_host, $server_port
#
#	Returns: array of output from iptables -L
#
#===============================================================================
sub tableList() {

	slog(7,"Starting: tableList");
	
	# connect to backend server
	my $conn;
	my $rpc;
	unless($IsTheServer) {
    	return rpc('callIptables',"L");
	} else {
		return callIptables("L");
	}

} # end of tableList()


#===============================================================================
#       T A B L E  F L U S H
#
#       Calls the server to flush (remove) the current iptables rules
#
#       Uses the global variables: $chain, $server_host, $server_port
#
#       Returns: true, if successful
#       Returns: false, if not successful
#
#===============================================================================
sub tableFlush($) {

	slog(7,"Starting: tableFlush");

	my $chain = shift;

	if (!defined $chain) {
		slog(3,"tableFlush did not recieve a chain name");
		slog(7,"Done: tableFlush");
		return(0);
	}
	unless($IsTheServer) {
        	# connect to backend server
	        my $conn;
        	my $rpc;
            rpc('callIptables',"F",$chain);
	} else {
		return callIptables("F",$chain);
	}
} # end of tableFlush()


#===============================================================================
#       T A B L E  I N S E R T
#
#       Calls the server to insert a rule into an iptables chain
#
#	Arg1:	ipv4 address
#	Arg2:	corresponding mac address
#	Arg3:	internal vlan number
#
#       Uses the global variables: $server_host, $server_port
#
#       Returns: true, if successful
#       Returns: false, if not successful
#
#===============================================================================
sub tableInsert($$$$) {

        slog(7,"Starting: tableInsert");

	# read in arguemnts
	my $srcip = shift;
        my $srcmac = shift;
        my $vlan = shift;
	my $authmethod = shift;

        slog(7,"Input: srcip: $srcip, srcmac: $srcmac, vlan: $vlan, authmethod: $authmethod");


	# verify we were called with reasonable arguemnts
	if (!isIpAddr($srcip)) {
		slog(3,"tableInsert Recieved invalid command input: $srcip");
                slog(7,"Done: tableInsert");
		return 0;
	}

	if (!isMacAddr($srcmac)) {
                slog(3,"tableInsert Recieved invalid command input: $srcmac");
                slog(7,"Done: tableInsert");
                return 0;
        }
	if ($vlan !~ m/\d+/) {
                slog(3,"tableInsert Recieved invalid command input vlan: $vlan");
                slog(7,"Done: tableInsert");
                return 0;
        }
       if ($authmethod !~ /^[rgts]/) {
                slog(3,"tableInsert Recieved invalid command input authmethod: $authmethod");
                slog(7,"Done: tableInsert");
                return 0;
        }

	
	unless($IsTheServer) {
	        # connect to backend server
        	my $conn;
	        my $rpc;
	        rpc('callIptables',"I",$srcip,$srcmac,$vlan,$authmethod);
	} else {
		return callIptables("I",$srcip,$srcmac,$vlan,$authmethod);
	}

} # end of tableInsert()


#===============================================================================
#	T A B L E  D E L E T E
#
#	deletes an entry from the current iptables ruleset
#
#	Arg1:	ipv4 address
#	Arg2:	mac address
#	Arg3:	internal vlan number
#       Arg4:   authmethod
#
#       Uses the global variables: $server_host, $server_port
#
#	Returns: true, if removal was successful
#	Returns: false, if removal was not successful
#
#===============================================================================
sub tableDelete($$$$) {

        slog(3,"Starting: tableDelete");

        # read in arguemnts
        my $srcip = shift;
        my $srcmac = shift;
	my $vlan = shift;
	my $authmethod = shift;

        slog(6,"Input: srcip: $srcip, srcmac: $srcmac, vlan: $vlan, authmethod: $authmethod");


        # verify we were called with reasonable arguemnts
        if (!isIpAddr($srcip)) {
                slog(3,"tableDelete Recieved invalid input, ipaddr: $srcip");
                slog(7,"Done: tableDelete");
                return 0;
        }

        if (!isMacAddr($srcmac)) {
                slog(3,"tableDelete Recieved invalid input, srcmac: $srcmac");
                slog(7,"Done: tableDelete");
                return 0;
        }
	if ($vlan !~ m/\d+/) {
                slog(3,"tableDelete Recieved invalid input, vlan: $vlan");
                slog(7,"Done: tableDelete");
                return 0;
        }

	unless($IsTheServer) {
	        # connect to backend server
        	my $conn;
	        my $rpc;
	        rpc('callIptables',"D",$srcip,$srcmac,$vlan,$authmethod);
	} else {
		return callIptables("D",$srcip,$srcmac,$vlan,$authmethod);
	}

} # end of tableDelete()



#===============================================================================
#	R U N  S E R V E R
#
#	starts up the server which runs as root to do iptables functions on our 
#	behalf.
#
#	Uses global variables $server_host and $server_port
#
#	Returns: true if starting the server was successful
#	Returns: false if starting the server was not successful
#
#===============================================================================
sub runServer() {
	slog(7,"Starting: runServer");

	$IsTheServer = 1;
    
	slog(7,"Done: runServer");
    return $server_port;
} # end of runServer()




#===============================================================================
#	S L O G
#
#   syslogs a message
#
#	Throughout captivator, we send lots of messages to syslog including
#	very verbose debug information, if set in the configuration section.
#	This subroutine is a wrapper routine to send the messages to syslog
#	if the message severity is worse (less than) the configured verbosity.
#
#	Arg1: integer containing the message severity level: 0 - 7.
#	Arg2: string containing the message to log.
#
#	Returns: true, always.
#
#===============================================================================
sub slog($$) {
    # Syslog Utility
    #
    # Argument 0:   message severity level:  0 - 7
    # Argument 1:   message to log
    #
    # Returns:      nothing
    #
    # This function sends messages to syslog and is called throughout
    # the program.  If the message we would send is worse (equal to or
    # less than the debug level we have set) then we send it.
   
    my $loglevel = shift;
    my $logmessage = shift;
    my $priority;

    if ($loglevel eq 7 ) { $priority = 'debug'; }
    if ($loglevel eq 6 ) { $priority = 'info'; }
    if ($loglevel eq 5 ) { $priority = 'notice'; }
    if ($loglevel eq 4 ) { $priority = 'warning'; }
    if ($loglevel eq 3 ) { $priority = 'err'; }
    if ($loglevel eq 2 ) { $priority = 'crit'; }
    if ($loglevel eq 1 ) { $priority = 'alert'; }
    if ($loglevel eq 0 ) { $priority = 'emerg'; }

	#print "loglevel: $loglevel, debugLvl: $debugLvl\n";

    my $logmsg2 = "[$priority] $logmessage";

    if ($loglevel le $debugLvl) {
		openlog('captivator','pid',$syslogfacility);
        syslog($priority,$logmsg2);
		closelog();
    }
	return(0);

} # end of slog()


#===============================================================================
#	I S  M A C  A D D R
#
#	Arg1: Scalar containing a string for us to check if it is a mac address
#	The mac address must be in DE:AD:12:34:be:ef style notation, *case insensitive*
#	
#	Returns: true, if arg1 is a mac address
#	Returns: false, if arg1 is not a mac address
#
#===============================================================================
sub isMacAddr($) {
	
	if ($_[0] =~ m/^[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}$/i) {
		return(1);
	} else {
		return(0);
	}
} # end of isMacAddr();



#===============================================================================
#   M A T C H  M A C  A D D R
#
#   Arg1: Scalar which could contain a mac address
#	The mac address must be in DE:AD:12:34:be:ef style notation, *case insensitive*
#
#	Returns: The mac address in Arg1, if there is one (case insensitive)
#	Returns: False, if there is no mac address in Arg1
#
#===============================================================================
sub matchMacAddr($) {

    if ($_[0] =~ m/[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}/i) {
        return($&);
    } else {
        return(0);
    }
} # end of matchMacAddr()



#===============================================================================
#   I S  I P  A D D R
#
#   Arg1: Scalar containing a string for us to check if it is an ip address
#			* The ip address must be in dotted quad notation *
#
#   Returns: true, if arg1 is an ip address
#   Returns: false, if arg1 is not an ip address
#
#===============================================================================
sub isIpAddr($) {
	#
	# returns true if the arguement to this function is an ip address
	# returns false otherwise
	
	if ($_[0] =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
		return(1);
	} else {
		return(0);
	}
} # end of isIpAddr()



#===============================================================================
#   M A T C H  I P  A D D R
#
#   Arg1: Scalar which could contain an ip address 
#			* The ip address must be in dotted quad notation *
#
#   Returns: The ip address in Arg1, if there is one
#   Returns: False, if there is no ip address in Arg1
#
#===============================================================================
sub matchIpAddr($) {
 
    if ($_[0] =~ m/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) {
        return($&);
    } else {
        return(0);
    }
} # end of matchIpAddr()



#===============================================================================
#   G E T  M A C  F R O M  A R P  T A B L E
#
#	Global var: $arpcmd
#
#	Arg1: Scalar containing an ip address in dotted quad notation
#
#	Returns: Scalar containing the mac address that corresponds to that
#		     IP address from the host's arp table.
#	Returns: False, if there was no corresponding entry
#
#	TODO:  	Modify getMacFromArpTabl to use /proc/net/arp instead of shelling 
#	out to /sbin/arp to increase performance and security
#
#===============================================================================
sub getMacFromArpTable($) {
	# given an ip address, look it up in the host's arp table
	# and return the corresponding mac address.

	my $iptomatch = shift;

	slog(7,"Starting: getMacFromArpTable");
	slog(7,"Input recieved: $iptomatch");

	# validate input arugument is an ip address, or we are wasting our time.
	if (!isIpAddr($iptomatch)) {
		slog(3,"getMacFromArpTable recieved invalid input");
		slog(7,"Done: getMacFromArpTable");
		return(0);
	} else {
		slog(7,"Input recieved seems valid.");
	}

	# dump the arp table
	unless (open(ARP, "< $arp_table")) {

    		slog(3,"There was an error reading the arp table: $arp_table.");
    		slog(4,"$!");
    		slog(7,"Done: getMacFromArpTable");
		close ARP;
    		return(0);
	}

	# now, let's process the arp table line by line to find 
	# the ip address we are looking for.

	# when we find it, we remember the mac address that is contained in that line.
	# we also notice if there are multiple matches just in case, 
	# but that probably can't happen in reality, at least not on linux.

	my $line;			# holds one line of text from the arp table
	my $match=0;    	# keep track of how many ip addresses we match.
	my $macaddr;    	# a place to put the matching mac address
	my $tmpipaddr=0;	# temporary ip address holder
	my $tmpmacaddr=0;	# temporary mac address holder

	#due to some strangeness, it seems better to slurp this all up
	local $/;
	my $arplines = <ARP>;
	close ARP;
	my @arptable = split /\n/,$arplines;

	for (@arptable) {

		# reset these temporary variables just to be safe
		$tmpipaddr=0;
		$tmpmacaddr=0;

		# get the line of text to look at
	    	my $line = $_;
		Captivator::slog(7,"arpline: $line");
		# read in the ip address in that line
		$tmpipaddr = matchIpAddr($line);

		# if the line contained an ip address,
	    	if ($tmpipaddr) {

			# if that's the ip address that we were looking for,
        		if ($tmpipaddr eq $iptomatch) {

            			$match += 1;
            			#print "$line";

				# read in the mac address on that line
				$tmpmacaddr = matchMacAddr($line);

		            	if ($tmpmacaddr) {
                			Captivator::slog(7,"Corresponds to mac address: $tmpmacaddr");
		                	$macaddr = $tmpmacaddr;

				# just in case there is no mac address in the line, or our regex's are wrong.
				# I don't think this will ever happen, but let's be prepared.
        		    	} else {
                			Captivator::slog(4,"Can't find a corresponding mac address in this line:");
		                	Captivator::slog(6,"$line");
            			}
        		}
    		}
	} # end while loop to process arp table
	#close ARP;	# close output from the external arp command

	# time to look at our result from the above processing and return
	# with the appropriate response.

	if ($match eq 0) {          # then nothing matched
	    	slog(3,"No IP address matched in the arp table");
    		slog(3,"Done: getMacFromArpTable");
	    	return(0);

	} elsif ($match eq 1) {     # then one mac address matched this ip address
    		slog(6,"Valid Pair: $iptomatch $macaddr");
	    	slog(3,"Done: getMacFromArpTable");
    		return($macaddr);
	} elsif ($match > 1) {      # then more than one entry matched!
    		slog(4,"What?!!  More than one mac was found for this IP in the table.  No way!");
			slog(7,"Done: getMacFromArpTable");
	    	return(0);
	}

	slog(3,"This point in getMacFromArpTable should never be reached");
	slog(7,"Done: getMacFromArpTable");
	return(0);

} # end of sub getMacFromArpTable


#===============================================================================
#
#	B U I L D  I P T A B L E S  C O M M A N D
#
#	puts together the arguments for iptables to do our bidding
#
#	Global var: %authnet
#	Global var: $iptables
#
#	Arg1: Scalar containing a character that specifies the iptables command:
#			L - list 			hint: see IPTABLES(8)
#			I - insert
#			D - drop
#
#	If Arg1 is 'I' or 'D', the following three arguments are required:
#
#	Arg2: Source ipv4 address 
#	Arg3: Source mac address
#	Arg4: Internal vlan number
#
#	Returns: Scalar containing the iptables command to be executed
#	Returns: False, if something went wrong
#
#===============================================================================
sub buildIptablesCmd($;$;$;$;$) {
	slog(7,"Starting: buildIptablesCmd");

	my $cmd = shift;
	my $srcip;
	my $srcmac;
	my $vlan;
	my $chain;
	my $authmethod;

	# verify our inputs are legit

	if ($cmd =~ m/^[ID]$/) {
		$srcip = shift if @_;
		$srcmac = shift if @_;
		$vlan = shift if @_;
		$authmethod = shift if @_;
		slog(7,"Input: cmd: $cmd, srcip: $srcip, srcmac: $srcmac, vlan: $vlan, authmethod: $authmethod");

	} elsif($cmd eq 'F') {
		$chain = shift;
		slog(7,"Input: cmd: $cmd, chain: $chain");

	} elsif($cmd eq 'L') {
		slog(7,"Input: cmd: $cmd");

	} else {
		slog(3,"buildIptablesCmd Recieved invalid command input: $cmd");
		slog(7,"Done: buildIptablesCmd");
		return 0;
	}



	# if the command is I or D, then srcip, srcmac, vlan are required
	if ($cmd =~ m/[ID]/) {
		if (!isIpAddr($srcip)) {
        		slog(3,"buildIptablesCmd Recieved invalid command input srcip: $srcip");
        		slog(7,"Done: buildIptablesCmd");
        		return 0;
    		}

    		if (!isMacAddr($srcmac)) {
        		slog(3,"buildIptablesCmd Recieved invalid command input srcmac: $srcmac");
        		slog(7,"Done: buildIptablesCmd");
        		return 0;
    		}

		if ($vlan !~ m/\d+/) {
                	slog(3,"buildIptablesCmd Recieved invalid command input vlan: $vlan");
                	slog(7,"Done: buildIptablesCmd");
                	return 0;
        	}
		if ($authmethod !~ /^[rstg]/) {
		        slog(3,"buildIptablesCmd Recieved invalid command input authmethod: $authmethod");
                        slog(7,"Done: buildIptablesCmd");
                        return 0;	
		}

		# get the iptables chain to manipulate based on the vlan number
		$chain = $authnet{$vlan}{"fw_chain"};

		if (! defined($chain)) {
			slog(3,"buildIptablesCmd: Can't find chain for vlan $vlan");
               		slog(7,"Done: buildIptablesCmd");
			return(0);
		}
	} # end if command is I or D

	# process command string

	my $retval;		# the string we will return

    if ( $cmd =~ /^[L]$/ ) {		# command is L, list
		$retval = "$iptablescmd -t nat -vnx -L";
		slog(7,"Command built: $retval");
        slog(7,"Done: buildIptablesCmd");
       	return($retval);
	}

	if ($cmd eq 'F') {
		$retval = "$iptablescmd -t nat -F $chain; $iptablescmd -t nat -A $chain -j RETURN";
		slog(6,"Command built: $retval");
        	slog(7,"Done: buildIptablesCmd");
       		return($retval);

    } else {	# command is I, insert, or D, delete
		# figure out mark
		if($markPackets) {
			my $m = 3;
			if($authmethod eq "radius") {
				$m = 1;
			} elsif($authmethod eq "guest") {
				$m = 2;
			} elsif($authmethod eq "superuser") {
				$m = 1;
			}
			$retval = "$iptablescmd -t nat -$cmd $chain -s $srcip -m mac --mac-source $srcmac -j $authmethod;"
			. "$iptablescmd -t mangle -$cmd POSTROUTING --src $srcip -j MARK --set-mark $m";
		} else {
			 $retval = "$iptablescmd -t nat -$cmd $chain -s $srcip -m mac --mac-source $srcmac -j $authmethod";
		}
        slog(6,"Command built: $retval");
        slog(7,"Done: buildIptablesCmd");
        return($retval);
	}

    slog(3,"This point in buildIptablesCmd should never be reached");
    slog(7,"Done: buildIptablesCmd");
    return(0);

} # end of buildIptablesCmd()


#===============================================================================
#
#       A R P  F O R  H O S T
#
#	- Calls backend server to run arping as a privledged user
#	- anayles results of the arping
#
#	Arg 1: ip address
#	Arg 2: vlan the ip address is on
#	Arg 3: the mac address we're looking for
#
#	Returns: true if the arps were successful, false if it was not successful
#
#===============================================================================
sub arpForHost($$$) {

        slog(7,"Starting: arpForHost");
        
        my $ip = shift;
        my $vlan = shift;
	my $mac = shift;

        slog(7,"Given ip: $ip, vlan: $vlan");

        # sanity check input
        if (!isIpAddr($ip)) {
                slog(4,"arpForHost was not given a valid IP address");
                slog(7,"Done: arpForHost");
                return(0);
        }

        if (! ($vlan =~ m/^\d+$/) ) {
                slog(4,"arpForHost was not given a valid vlan");
                slog(7,"Done: arpForHost");
                return(0);
        }

        if (!isMacAddr($mac)) {
                slog(4,"arpForHost was not given a valid MAC address");
                slog(7,"Done: arpForHost");
                return(0);
        }


        my @result;
	
	unless($IsTheServer) {
		# connect to backend server
        	my $conn;
	        my $rpc;
        	rpc('do_arpForHost',$ip,$vlan);
	} else {
		@result = do_arpForHost($ip,$vlan);
	}
	my $heardfromarp=0;	# 0 means we have not heard from them via arp

	# now, let's process the arping output

	foreach my $arpline (@result) {

		# match the arp responses
                # this should match the output of both versions of arping I know of
                if (($arpline =~ m/bytes from/) || ($arpline =~ m/Unicast reply from/)) {

			# get the mac address that answered
                        my $line = uc($_);
                        my $tmpmac = matchMacAddr($line);
                                        
                        # if a different mac address responds to this IP than we expected
			# this is probably rare, or never happens
                        if ($tmpmac ne $mac) {
                                slog(5,"arping got $tmpmac for $ip, expected $mac.  Removing stale ip-mac pairing.");
                                $heardfromarp=0;

                        } else {
                                # otherwise the correct mac address responded
                                slog(5,"expireHosts found $tmpmac, $ip still alive via arp");
                                $heardfromarp=1;
                        }
		} # end match arp responses

	} # end of foreach to process arping output

        slog(7,"Heard from arp: $heardfromarp");
        slog(7,"Done: arpForHost");

	# return whether or not we will heard them
	return($heardfromarp);

} # end of arpForHost



#===============================================================================
#
#       D O  A R P  F O R  H O S T
#
#       	 shells out to arping as a privledged user
#
#	Arg 1: ip address
#	Arg 2: vlan the ip address is on
#
#	Returns: An array containing the output of the arping command
#
#===============================================================================
sub do_arpForHost($$) {

	slog(7,"Starting: do_arpForHost");

	my $ip = shift;
	my $vlan = shift;

	slog(7,"Given ip: $ip, vlan: $vlan");

	# sanity check input
	if (!isIpAddr($ip)) {
		slog(4,"do_arpForHost was not given a valid IP address");
		slog(7,"Done: do_arpForHost");
		return(0);
	}

	if (! ($vlan =~ m/^\d+$/) ) {
		slog(4,"do_arpForHost was not given a valid vlan");
		slog(7,"Done: do_arpForHost");
		return(0);
	}

        # get the firewall's interface name for that internal vlan
        my $interface = $authnet{$vlan}{'br_int'};

        # note that the -i or -I is part of the variable $arpopts from the
        # config file.  This is because we found that there is more than
        # one version of arping in the wild that have different options for
        # specifing the interface.

        my $arpget = "$arping $ip $arpopts $interface";
        slog(7,"arpcmd: $arpget");

        # shell out to arping the mac address
        # the security aspect of this is not too bad since there is
        # zero user entered data here.

        unless (open(ARPING,"$arpget |")) {
		#print @errlines;
                slog(5,"do_arpForHost: There was an error calling $arping");
                slog(5,"arpcmd: $arpget");
                slog(5,"$!");
                slog(7,"Done: do_arpForHost");
                return(0);
        }

	# slurp the result into an array for further processing
	# that should be done as an unprivledged user
	my @result = <ARPING>;

	close ARPING;

	return @result;

} # end of do_arpForHost


#===============================================================================
#
#	C H E C K  A U T H Z
#
#	calls other methods for authorization, one after the next
#	for example, try the built-in database first, then ldap
#
#	You could also add additional authorization checks here.
#
#	Arg 1: a username
#	Arg 2: a mac address (optional)
#
#	Returns: 
#		true, if they are authorized or
#		false, and a string containing the reason to show the user
#
#===============================================================================
sub checkAuthZ($$) {

	my $username = shift;
	my $mac = shift;

	my $auth_captivator =0;
	my $message = '';

	($auth_captivator,$message) = checkCaptivatorAuthZ($username,$mac);

	if ($auth_captivator eq 0) {
		slog(7,"Authorization by checkCaptivatorAuthZ failed");
		return(0,$message);
	} else {
                slog(7,"Authorization by checkCaptivatorAuthZ successful");
        }

	if ($UseLDAPAuthZ) { 
		if (! (checkLDAPAuthZ($username)) ) {
			slog(7,"Authorization by checkCaptivatorAuthZ failed");
			return(0,"Sorry, you are not authorized to log in.");
		}
	}

	return(1);

} # end of checkAuthZ



#===============================================================================
#
#       C H E C K  L D A P  A U T H - Z
#
#	Arg 1: username
#	Returns: true if authorized, false of not authorized
#
#===============================================================================

sub checkLDAPAuthZ($) {

	my $username = shift;

	return 1;

} # end of checkLDAPAuthZ


#===============================================================================
#
#       C H E C K  C A P T I V A T O R  A U T H Z
#
#	checks Captivator-gw's questionable built in authz feature.
#
#	Captivator-gw's built in authz can run in either default permit
#	or default deny modes, as specified in the config file by AuthzMode.
#
#	In default permit mode, the 'userauthz' SQL table is used
#	as a blacklist.  If a username or a mac address exists in the table,
#	then the authorization fails.
#
#	In default deny mode, the 'userauthz' SQL table is used as a 
#	whitelist.  A username must exist in the table for successful 
#	authorization.  I am not sure what good using a mac address in the 
#	whitelist would be.
#
# 	If there both denys and permits, the deny wins.
#	If there is more than one deny, the 1st deny found wins.
#
#	In theory, it would be easy to hack in that the username and mac
#	address both must match.  If you need that, write that code yourself
#	and send me a patch :-)
#
#	Arg 1: a username
#	Arg 2: a mac address (optional)
#
#	Returns: 
#		true, if they are authorized or
#		false, and a string containing the reason to show the user
#
#
#===============================================================================
sub checkCaptivatorAuthZ($$) {

	slog(7,"Starting: checkCaptivatorAuthZ");

	# read in variables
	my $username = shift;
	my $macaddr = shift;

	# a sanity check
	untaint_usernm($username);

	if (!defined($username)) {
		slog(4,"checkCaptivatorAuthZ called with a bunk username");
		slog(7,"Done: checkCaptivatorAuthZ");
		return(0);
	}

	if (!isMacAddr($macaddr)) { # optional
		# note that zero is not the same as SQL NULL
		$macaddr = '0';
	}

	slog(7,"Given user: $username, macaddr: $macaddr");

	# connect to database
        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"checkCaptivatorAuthZ couldn't connect to the database");
                return(0);
        }

        # read entries from sql table 
        my $sth;
        eval { $sth = $dbh->prepare("SELECT auth_id,authorize,extreason,intreason FROM userauthz WHERE username=? OR macaddr=? ORDER BY authorize"); } ;
        if ($@) {
                slog(3,"checkCaptivatorAuthZ couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: checkCaptivatorAuthZ");
                $sth->finish();
                return(0);
        };

        eval { $sth->execute($username,$macaddr); };
        if ($@) {
                slog(3,"checkCaptivatorAuthZ couldn't query the userauthz table");
                slog(4,$sth->errstr);
                slog(7,"Done: checkCaptivatorAuthZ");
                $sth->finish();
                return(0);
        };

        my ($auth_id,$authorize,$extreason,$intreason);
        $sth->bind_columns(\($auth_id,$authorize,$extreason,$intreason));

        $sth->fetch;

	# since the sql output is sorted by authorize, (meaning that it 
	# should return 0's first and 1's last) the first deny line
	# returned by the sql query wins!  So, if there is a deny, this
	# should find it.  If there is more than one deny, well then
	# change this code if you want different behavior.

	# however, if we are in default permit mode, if there are no
	# rows at all retured from the database, then the user is
	# authorized because that's ok.

	my $allowed;

	if ($AuthZMode) { 	# if we are in default permit mode:
		$allowed=1;

		if ($authorize eq 0) { 
			$allowed=0; 
			slog(5,"checkCaptivatorAuthZ found that $username is not authorized (for $intreason)");
		}

		if ($auth_id eq 0) { $allowed=1; } # no rows were returned


	} else {	 	# we are in default deny mode:
		$allowed=0;

		if ($authorize eq 0) { 
			$allowed=0; 
			slog(5,"checkCaptivatorAuthZ found that $username is not authorized");
		}

		if ($authorize eq 1) { $allowed=1; }
	}

	# return if they were allowed or not, and optionally a reason to show them
	if (defined($extreason)) {
		return($allowed,$extreason);
	} else {
		return($allowed);
	}


} # end of checkCaptivatorAuthZ


sub untaint_usernm($) {

        my $user = shift;

        #first make sure it's not blank
        return undef if $user =~ /^$/;

	# save a copy of this
	my $usersaved = $user;

        # some sanity check rules for usernames:

        # username can only be <= 20 characters
        ########$user = substr($user,0,19);

        # usernames can only contain these characters
        my $allowedchars = 'A-Za-z0-9\_\ \-@.';
        $user =~ s/[^${allowedchars}]//g;

	if ($user ne $usersaved) {
		slog(4,"FILTERED username: $usersaved with illegal characters!");
	}
        return($user);

} # end of untaint_usernm()



sub untaint_passwd($$) {

        my $pass = shift;
	my $user = shift;
	my $passsaved = $pass;

        #first make sure it's not blank
        return undef if $pass =~ /^$/;

        # some sanity check rules for passwords:

        # passwords can only be <= 20 characters
        #$pass = substr($pass,0,19);

        # passwords can only contain keyboard characters
        my $allowedchars = 'A-Za-z0-9\ \`\~\!\@\#\$\%\^\&\*\(\)\-\_\=\+\[\]\{\}\;\'\:\"\,\.\/\<\>\?\\\|';
        $pass =~ s/[^${allowedchars}]//g;

	if ($pass ne $passsaved) {
		slog(4,"FILTERED password for $user with illegal characters!");
	}
        return($pass);

} # end of untaint_passwd()

sub printInvalidEmailPage() {
        open(EMAIL,"< $workingdir/umichemail.html")
              or die "Couldn't open $workingdir/umichemail.html for reading: $!";
        while(<EMAIL>) {
		print;
	}
        close(EMAIL);
}


sub printSysErrPage() {

        open(SYSERR,"< $templatedir/$syserr_tmpl")
              or die "Couldn't open $templatedir/$syserr_tmpl for reading: $!";

	# slurp in whole file
	local $/;
	my $syserr = <SYSERR>;
	close(SYSERR);


        open(TEMPLATE,"< $templatedir/$main_tmpl")
              or die "Couldn't open $templatedir/$main_tmpl for reading: $!";
        while (<TEMPLATE>) { 
		s/CAPTIVATOR-LEFT-NAV-BAR/$syserr/;
		s/CAPTIVATOR-VERSION/Captivator-gw $VERSION/;
		print; 
	}

        close(TEMPLATE);


}

sub printBlockedPage($) {
        my $extreason = shift;

        open(BLOCKED,"< $templatedir/$blocked_tmpl")
              or die "Couldn't open $templatedir/$blocked_tmpl for reading: $!";

	# slurp in whole file
	local $/;
	my $blocked = <BLOCKED>;
	close(BLOCKED);
        
        $blocked =~ s/CAPTIVATOR-BLOCK-EXTREASON/$extreason/;

        open(TEMPLATE,"< $templatedir/$main_tmpl")
              or die "Couldn't open $templatedir/$main_tmpl for reading: $!";
        while (<TEMPLATE>) { 
		s/CAPTIVATOR-LEFT-NAV-BAR/$blocked/;
		s/CAPTIVATOR-VERSION/Captivator-gw $VERSION/;
		print; 
	}

        close(TEMPLATE);


}


sub printRedirectPage($$$) {

	my $redirect = shift;
	my $authtype = shift;
	my $from_email = shift;

	slog(7,"printRedirectPage redirecting to $redirect");

        open(CHANGEPW,"< $templatedir/changepass.tmpl") ##TODO FIX THIS
              or die "Couldn't open $templatedir/changepass.tmpl for reading: $!";

        #slurp in whole file
        local $/;
        my $changepw = <CHANGEPW>;
        close(CHANGEPW);
        
	my $meta = '<meta http-equiv="refresh" content="1;url=REDIRECT-VARIABLE">';
	my $gues = 'You may change your MaizePort password below, or continue to <a href="REDIRECT-VARIABLE">REDIRECT-VARIABLE</a>.';
	my $radi = 'You will be redirected to <a href="REDIRECT-VARIABLE">REDIRECT-VARIABLE</a> in <span id="cntdwn">1</span> seconds.<br/>';

        open(PAGE,"< $workingdir/$redirect_page")
              or die "Couldn't open $workingdir/$redirect_page for reading: $!";
        while (<PAGE>) {
	     if ($authtype eq 'guest') {
		  s/CAPTIVATOR-META-REFRESH//;
		  s/CAPTIVATOR-REDIRECT-TEXT/$gues/;
                  s/CAPTIVATOR-CHANGE-GUEST-PASSWORD/$changepw/;
              } else {
		  s/CAPTIVATOR-META-REFRESH/$meta/;
                  s/CAPTIVATOR-REDIRECT-TEXT/$radi/;
                  s/CAPTIVATOR-CHANGE-GUEST-PASSWORD//;
              } 
              s/REDIRECT-VARIABLE/$redirect/g;
	      s/CAPTIVATOR-VERSION/Captivator-gw $VERSION/;
              print;
        }
	close(PAGE);
}

sub printGuestNoticePage($) { 
    my $redirect = shift;
    open(GST,"< $templatedir/$guest_page")
        or die "Couldn't open $templatedir/$guest_page for reading: $!";
    while(<GST>) {
        print;
    }
    close(GST);
}

sub printAllreadyLoggedInPage() {

        open(ERR,"< $templatedir/$loggedinallready_tmpl")
              or die "Couldn't open $templatedir/$loggedinallready_tmpl for reading: $!";

        # slurp in whole file
        local $/;
        my $err = <ERR>;
        close(ERR);

        open(TEMPLATE,"< $templatedir/$main_tmpl")
              or die "Couldn't open $templatedir/$main_tmpl for reading: $!";
        while (<TEMPLATE>) {
                s/CAPTIVATOR-LEFT-NAV-BAR/$err/;
		s/CAPTIVATOR-VERSION/Captivator-gw $VERSION/;
                print;
        }

        close(TEMPLATE);

}

sub printLoginFailedPage($$) {

	my $redir = shift;
	my $serverhost = shift;

	slog(7,"printLoginFailedPage host: $serverhost redir: $redir");

        open(ERR,"< $templatedir/$loginfailed_tmpl")
              or die "Couldn't open $templatedir/$loginfailed_tmpl for reading: $!";

        # slurp in whole file
        local $/;
        my $err = <ERR>;
        close(ERR);

	$err =~ s/REDIRECT-VARIABLE/$redir/g;
	$err =~ s/POST-VARIABLE/$serverhost/g;

        open(TEMPLATE,"< $templatedir/$main_tmpl")
              or die "Couldn't open $templatedir/$main_tmpl for reading: $!";
        while (<TEMPLATE>) {
		s/REDIRECT-VARIABLE/$redir/g;
                s/CAPTIVATOR-LEFT-NAV-BAR/$err/g;
		s/POST-VARIABLE/$serverhost/g;
		s/CAPTIVATOR-VERSION/Captivator-gw $VERSION/;
                print;
        }

        close(TEMPLATE);


}

sub printLoginForm($$) {

	my $redir = shift;
	my $serverhost = shift;

	slog(7,"printLoginPage host: $serverhost redir: $redir");

        open(LOGIN,"< $templatedir/$login_tmpl")
              or die "Couldn't open $templatedir/$login_tmpl for reading: $!";

        # slurp in whole file
        local $/;
        my $login = <LOGIN>;
        close(LOGIN);

        $login =~ s/REDIRECT-VARIABLE/$redir/g;
        $login =~ s/POST-VARIABLE/$serverhost/g;

        open(TEMPLATE,"< $templatedir/$main_tmpl")
              or die "Couldn't open $templatedir/$main_tmpl for reading: $!";
        while (<TEMPLATE>) {
                s/CAPTIVATOR-LEFT-NAV-BAR/$login/;
		s/CAPTIVATOR-VERSION/Captivator-gw $VERSION/;
        	s/REDIRECT-VARIABLE/$redir/g;
        	s/POST-VARIABLE/$serverhost/g;
                print;
        }

        close(TEMPLATE);


} # end of printLoginForm()


#############################################################################
#
#	S T A R T  U P  D B
#
#	Cleans the mysql database for startup.  If there are stale entries
#	in the currentusers or expireusers database, then captivator may
#	think that a user is allready logged in.
#
#############################################################################
sub startupDB() {

	slog(7,"Starting: startupDB to clean database before initilizing");

        my $dbh = dbConnect();
        if (!$dbh) { # couldn't connect to database
                slog(3,"startupDB couldn't connect to the database");
                return(0);
        }

	my $sth;

        eval { $sth = $dbh->prepare("DELETE b FROM currentusers AS a, expireusers AS b WHERE a.fwhostname=? AND a.user_id = b.user_id"); };
        if ($@) {
                slog(3,"startupDB couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: startupDB");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        eval { $sth->execute($fw_hostname); };
        if ($@) {
                slog(3,"startupDB couldn't delete from the currentusers table");
                slog(4,$sth->errstr);
                slog(7,"Done: startupDB");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

	$sth->finish();
	my $sth2;

        eval { $sth2 = $dbh->prepare("DELETE FROM currentusers WHERE fwhostname=?"); };
        if ($@) {
                slog(3,"startupDB couldn't prepare the select sql statement");
                slog(4,$dbh->errstr);
                slog(7,"Done: startupDB");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

        eval { $sth2->execute($fw_hostname); };
        if ($@) {
                slog(3,"startupDB couldn't delete from the currentusers table");
                slog(4,$sth2->errstr);
                slog(7,"Done: startupDB");
                $sth->finish();
                $dbh->disconnect;
                return(0);
        };

	$sth2->finish();

}


#############################################################################
# 
#	R A D I U S  A C C T
#
#	sends radius accounting start & stop messages to a central
#	radius accounting server
#
#	Arguments:
#
#	1) username	# the username
#	2) user_id	# the user's unique id number from the db
#	3) mac_addr	# the user's mac address
#	4) ip_addr	# the user's mac address
#	5) startstop	# 1 for start, 0 for stop
#
#	Uses global variables for the NAS IP address, Radius Accounting
#	server ip address, and shared secret key
#
#############################################################################
sub radiusAcct($$$$$) {

	# if there is not a radius server defined, that's ok, just abort.
	if ($Radius_acct_server eq '') { return(0); }

	my $username = shift;
	my $session_id = shift;
	my $macaddr = shift;
	my $v4ip = shift;
	my $startstop = shift;

	if (! (defined($username))) { slog(3,"radiusAcct didn't recieve argument: username"); return(0); }
	if (! (defined($session_id))) { slog(3,"radiusAcct didn't recieve argument: session_id"); return(0); }
	if (! (defined($macaddr))) { slog(3,"radiusAcct didn't recieve argument: macaddr"); return(0); }
        if (! (defined($v4ip))) { slog(3,"radiusAcct didn't recieve argument: v4ip"); return(0); }
	if (! (defined($startstop))) { slog(3,"radiusAcct didn't recieve argument: startstop"); return(0); }

	slog(7,"radiusAcct recieved user: $username, id: $session_id, mac: $macaddr, ip: $v4ip, start: $startstop");

	# the radius Acct-Session-Id can convinently be the user_id from the 
	# currentusers sql table, since those entries have to be unique.

	# we will send the mac address of the user as the Calling-Station-Id,
	# because that's what Airwave's AMP product wants.

	my $r;

	# connect to the radius account server
	$r  = Authen::Radius->new(
	  Host	  => $Radius_acct_server,
          Secret  => $Radius_acct_secret,
          Service => 'radius-acct',
          Accounting  => 1
	);

	if (! $r) {
	  my $errmsg = $r->strerror();
	  slog(5,"Problem with radius accounting: $errmsg");
	}

	$r->clear_attributes;

	if ($startstop) { 	# this is a start message

	  $r->add_attributes( { Name => 1, Value => $username, Type => 'string' },	# User-Name
            { Name => 4, Value => $radius_nasip, Type => 'ipaddr' }, 			# NAS-IP-Address
            { Name => 8, Value => $v4ip, Type => 'ipaddr' },    			# Framed-IP-Address
            { Name => 31, Value => $macaddr, Type => 'string' },    			# Calling-Station-Id
            { Name => 40, Value => '1', Type => 'integer' },          			# Acct-Status-Type(Start)
            { Name => 40, Value => '7', Type => 'integer' },        			# Acct-Status-Type(Accounting-On)
            { Name => 44, Value => $session_id, Type => 'string'},            		# Acct-Session-Id
          );

	} else {		# this is a stop message

	  $r->add_attributes({ Name => 1, Value => $username, Type => 'string' },   	# User-Name
            { Name => 4, Value => $radius_nasip, Type => 'ipaddr' }, 			# NAS-IP-Address
            { Name => 8, Value => $v4ip, Type => 'ipaddr' },            		# Framed-IP-Address
            { Name => 31, Value => $macaddr, Type => 'string' },        		# Calling-Station-Id
            { Name => 40, Value => '2', Type => 'integer' },             		# Acct-Status-Type(Stop)
            { Name => 40, Value => '8', Type => 'integer' },             		# Acct-Status-Type(Accounting-Off)
            { Name => 44, Value => $session_id, Type => 'string'},              	# Acct-Session-Id
          );

	  # if we knew these, they could be sent...
	  #  { Name => 42, Value => InOctets, Type => 'integer' },                 # Acct-Input-Octets
	  #  { Name => 43, Value => OutOctets, Type => 'integer' },                # Acct-Output-Octets
          #  { Name => 46, Value => session_time, Type => 'integer' }              # Acct-Session-Time

	} # end of stop message

	# ok, now send the accounting packet and see if we get an
	# accounting-response packet back.

    	if($r->send_packet('4')) {

          my $radiusresult = $r->recv_packet;

          if(defined($radiusresult)) {	# everything is then ok
	    return(1);

          } else {	# if we didn't get a reply
	    my $errmsg = $r->strerror();
	    slog(5,"Problem with radius accounting recieve for user $username $errmsg $radiusresult");
	    return(0);
	  }

        } else {	# if we couldn't send the packet
            my $errmsg = $r->strerror();
            slog(5,"Problem with radius accounting send for user $username $errmsg");
            return(0);

        } # end 

	slog(5,"Radius accounting failed, bad position in code!");
	return(0);
}

sub rpcsecret() {
	return $rpcsecret;
}

# my favorite number is
53;
